import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.xml.sax.SAXException;

import com.alibaba.fastjson.JSONObject;
import com.huoli.pasettle.app.SettleAppMain;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SettleAppMain.class)
public class TestDynamicMapper {

	@Autowired
	@Qualifier("skypaySqlSessionFactory")
	private SqlSessionFactory sessionFactory;

	static String script = "SELECT codetype,value,countfx(aaa) FROM PAYINFO_DICT_x WHERE  1 = 1 "
			+ " <if test=\"codeType != null and codeType != ''\"> " + "    and CODETYPE = #{codeType} " + " </if> ";
			//+ " <if test=\"value != null and value != ''\"> " + " and VALUE = #{value} "
			//+ " </if>  and VALUE = #{value} ";

	public static String wrapScript(String script) {
		return "<script>" + script + "</script>";
	}

	@Test
	public void testMapper3() throws SQLException {

		LanguageDriver languageDriver = new XMLLanguageDriver();
		Configuration configuration = new Configuration();
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, wrapScript(script), Object.class);
		Map<String, String> parameters = new HashMap<>();
		parameters.put("codeType", "ip_white_list");
		//parameters.put("value", "10.0.1.1");
		BoundSql boundSql = sqlSource.getBoundSql(parameters);

		// BoundSql countSql = new BoundSql(statement.getConfiguration(), "select
		// count(*) from (" + boundSql.getSql() + ") temp",
		// boundSql.getParameterMappings(), params);

		String sql = boundSql.getSql();
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

		MetaObject metaObject = configuration.newMetaObject(parameters);
		SqlSession sqlSession = sessionFactory.openSession();
		Connection conn = sqlSession.getConnection();
		PreparedStatement ps = conn.prepareStatement(sql);
		for (int i = 0; i < parameterMappings.size(); i++) {
			ParameterMapping parameterMapping = parameterMappings.get(i);
			String propertyName = parameterMapping.getProperty();
			// Object value = metaObject.getValue(propertyName);
			Object value = parameters.get(propertyName);
			TypeHandler typeHandler = parameterMapping.getTypeHandler();
			typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
		}
		ResultSet rs = ps.executeQuery();
		ResultSetMetaData metaData = rs.getMetaData();
		final int columnCount = metaData.getColumnCount();
		for (int i = 1; i <= columnCount; i++) {
			// columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i)
			// : metaData.getColumnName(i));
		    // jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
			// classNames.add(metaData.getColumnClassName(i));
			System.out.println("columnName=" + metaData.getColumnName(i) + ",columnType=" + metaData.getColumnType(i) + "," + metaData.getColumnClassName(i));
		}
		
		 DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
		 final DefaultResultHandler resultHandler = new DefaultResultHandler(configuration.getObjectFactory());
		 resultHandler.handleResult(resultContext);
		 
		while(rs.next()) {
			for (int i = 1; i <= columnCount; i++) {
				
			}
		}
		
		/*
		 * ResultMap resultMap = new ResultMap.Builder(configuration, "ccc", Map.class,
		 * new ArrayList<ResultMapping>(), true).build(); // ResultMap resultMap = //
		 * sqlSession.getConfiguration().getResultMap("userResultMap"); //
		 * ResultSetHandler<?> resultSetHandler = new //
		 * DefaultResultSetHandler(configuration, resultMap,null); // User user =
		 * resultSetHandler.handleResultSets(rs); // Executor executor, MappedStatement
		 * mappedStatement, ParameterHandler // parameterHandler, ResultHandler<?>
		 * resultHandler, BoundSql boundSql,RowBounds // rowBounds // ResultSetHandler
		 * resultSetHandler = new //
		 * DefaultResultSetHandler(configuration.getObjectWrapperFactory());
		 * 
		 * // configuration.addMappedStatement(statement); Environment environment =
		 * configuration.getEnvironment(); TransactionFactory transactionFactory =
		 * getTransactionFactoryFromEnvironment(environment);
		 * 
		 * Transaction tx =
		 * transactionFactory.newTransaction(environment.getDataSource(),
		 * TransactionIsolationLevel.REPEATABLE_READ, false); SimpleExecutor executor =
		 * new SimpleExecutor(configuration, tx); // List<Object> list =
		 * executor.query(statement, 16, RowBounds.DEFAULT, null);
		 */
		/*
		 * List<Object> list = executor.doQuery(statement, parameters,
		 * RowBounds.DEFAULT, null, sqlSource.getBoundSql(parameters));
		 * System.out.println(JSONObject.toJSONString(list));
		 */
	}
	
	 

	/**
	 * 代入参数值
	 * 
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
		parameterHandler.setParameters(ps);
	}

	/**
	 * 显示最终执行SQL
	 */
	@Test
	public void testMapper2() {
		String script = "<script> select codetype,value,countfx(aaa) "
				+ " FROM PAYINFO_DICT_x where 1 = 1 "
				+ "<if test='codeType != null'>and CODETYPE = #{codeType} </if>"
				+ " <if test='dayx != null '> and day=#{dayx,jdbcType=DATE}</if>"
				+ " <if test='timex != null '> and time=#{timex,jdbcType=TIMESTAMP}</if>"
				+ "</script>";

		LanguageDriver languageDriver = new XMLLanguageDriver();
		Configuration configuration = new Configuration();
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);
		Map<String, Object> parameters = new HashMap<>();
		parameters.put("codeType", "ip_white_list");
		parameters.put("dayx", new Date());
		parameters.put("timex", new Date());
		BoundSql boundSql = sqlSource.getBoundSql(parameters);
		String sql =  boundSql.getSql();
		System.out.println("sql=" + sql);
		 

        String PS4 = "[a-z]+(#(\\{(.*?)}))";
        Pattern pattern = Pattern.compile(PS4);
        Matcher matcher = pattern.matcher(sql);
        
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		
		for (int i = 1; matcher.find(); i++) {
            String g1 = matcher.group(i);
            sql = sql.replace(g1, "?");
        } 
		
		for (int i = 0; i < parameterMappings.size(); i++) {
			ParameterMapping parameterMapping = parameterMappings.get(i);
			String propertyName = parameterMapping.getProperty();
			// Object value = metaObject.getValue(propertyName);
			Object value = parameters.get(propertyName);
			TypeHandler typeHandler = parameterMapping.getTypeHandler();
			JdbcType jdbcType = parameterMapping.getJdbcType();
			if(JdbcType.DATE.equals(jdbcType)) {
				System.out.println("date type:" + propertyName);
			}else if(JdbcType.TIMESTAMP.equals(jdbcType)) {
				System.out.println("timestamp type:" + propertyName);
			}else if(JdbcType.TIME.equals(jdbcType)) {
				System.out.println("time type:" + propertyName);
			}
			//typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
		}
		
		
		//System.out.println(buildSql(script));

		// Map<String, Object> paramMap = new HashMap<>();
		// paramMap.put("id", 1);
		// BoundSql boundSql =
		// sqlSession.getConfiguration().getMappedStatement("getUserById").getBoundSql(paramMap);

	}

	private static String buildSql(String script) {

		LanguageDriver languageDriver = new XMLLanguageDriver();
		Configuration configuration = new Configuration();
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);
		Map<String, String> parameters = new HashMap<>();
		parameters.put("codeType", "ip_white_list");
		BoundSql boundSql = sqlSource.getBoundSql(parameters);
		return boundSql.getSql();
	}

	public List<MyObject> getMyObjects() {
		SqlSession sqlSession = sessionFactory.openSession();
		try {
			Configuration configuration = sqlSession.getConfiguration();
			MappedStatement.Builder builder = new MappedStatement.Builder(configuration, "myNamespace.getMyObjects",
					null, SqlCommandType.SELECT);
			builder.resultMaps(Arrays
					.asList(new ResultMap.Builder(configuration, "myResultMap", MyObject.class, Collections.emptyList())
							.build()));
			builder.resultSetType(ResultSetType.FORWARD_ONLY);
			builder.fetchSize(100);
			builder.statementType(StatementType.PREPARED);
			builder.resulSets(null);

			// 添加结果类型转换
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			typeHandlerRegistry.register(MyObject.class, new MyTypeHandler());

			MappedStatement mappedStatement = builder.build();
			List<MyObject> results = sqlSession.selectList("myNamespace.getMyObjects");
			sqlSession.commit();
			return results;
		} finally {
			sqlSession.close();
		}
	}

	public class MyObject {
		private Long id;
		private String name;
		private Integer value;

		// 省略getter和setter方法
	}

	public class MyTypeHandler implements TypeHandler<MyObject> {
		@Override
		public void setParameter(PreparedStatement ps, int i, MyObject parameter, JdbcType jdbcType)
				throws SQLException {
			// 设置参数
		}

		@Override
		public MyObject getResult(ResultSet rs, String columnName) throws SQLException {
			// 获取结果
			return null;
		}

		@Override
		public MyObject getResult(ResultSet rs, int columnIndex) throws SQLException {
			// 获取结果
			return null;
		}

		@Override
		public MyObject getResult(CallableStatement cs, int columnIndex) throws SQLException {
			// 获取结果
			return null;
		}

	}

	@Test
	public void testMapper() throws SQLException, SAXException, IOException, ParserConfigurationException {
		Configuration configuration = sessionFactory.getConfiguration();
		Map<String, Object> params = new HashMap<>();
		params.put("codeType", "ip_white_list");
		params.put("value", "10.0.1.1");
		// 默认languageDriver is XMLLanguageDriver
		// org.apache.ibatis.builder.xml.XMLConfigBuilder.settingsElement(Properties)
		// 设置语言驱动
		LanguageDriver languageDriver = configuration.getLanguageRegistry().getDefaultDriver();

		// 事务工厂 见 org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory()
		// this.transactionFactory = new SpringManagedTransactionFactory();
		// TransactionFactory transactionFactory =
		// configuration.getEnvironment().getTransactionFactory();
		// XMLLanguageDriver languageDriver = new XMLLanguageDriver();

		// issue #127
		/*
		 * script = PropertyParser.parse(script, configuration.getVariables());
		 * TextSqlNode textSqlNode = new TextSqlNode(script);
		 */
		// SqlSource sqlSource = new DynamicSqlSource(configuration, textSqlNode);
		SqlSource sqlSource = languageDriver.createSqlSource(configuration, wrapScript(script), params.getClass());
		/*
		 * Document document = newDocumentBuilder().parse(new InputSource(new
		 * StringReader(script))); XNode xNode = new XNode(new XPathParser(script),
		 * document.getFirstChild(), configuration.getVariables());
		 * 
		 * XMLScriptBuilder builder = new XMLScriptBuilder(configuration, xNode,
		 * Map.class);
		 */
		// SqlSource sqlSource = builder.parseScriptNode();

		/*
		 * SqlSourceBuilder sourceBuilder = new SqlSourceBuilder(configuration);
		 * SqlSource sqlSource = sourceBuilder.parse(script,Map.class,null) ;
		 */
		String statementId = "demo_111";
		ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId, Map.class,
				new ArrayList<ResultMapping>(), true).build();

		// KeyGenerator keyGenerator = entityMapper.autoId() ? new Jdbc3KeyGenerator() :
		// new
		// NoKeyGenerator();

		// 构建语句
		MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, statementId, sqlSource,
				SqlCommandType.SELECT)
						// .resource(resource)
						// .fetchSize(fetchSize)
						// .timeout(timeout)
						.statementType(StatementType.PREPARED).keyGenerator(NoKeyGenerator.INSTANCE)
						// .keyProperty(keyProperty)
						// .keyColumn(keyColumn)
						// .databaseId(databaseId)
						.lang(languageDriver).resultOrdered(false).resultMaps(Arrays.asList(inlineResultMap))
		// .resultSetType(ResultSetType.SCROLL_INSENSITIVE)

		// .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
		// .useCache(valueOrDefault(useCache, isSelect))
		// .cache(currentCache);
		;

		// 设置参数映射
		List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
		ParameterMap parameterMap = new ParameterMap.Builder(configuration, statementId, params.getClass(),
				parameterMappings).build();
		// ParameterMap statementParameterMap = parameterMap;
		statementBuilder.parameterMap(parameterMap);
		MappedStatement statement = statementBuilder.build();

		SqlSource sqlSouce = statement.getSqlSource();
		BoundSql boundSqlx = sqlSource.getBoundSql(params);

		String sql = boundSqlx.getSql();
		System.out.println("sql=" + sql);

		configuration.addMappedStatement(statement);

		boolean isExists = configuration.hasStatement(statementId);

		if (configuration.getMappedStatement(statementId) != null) {
			System.out.println(statementId + ",存在");
		}
		// MappedStatement执行方式一
		// MappedStatement mappedStatement =
		// configuration.getMappedStatement("getUserById");
		BoundSql boundSql = statement.getBoundSql(params);
		String sql2 = boundSqlx.getSql();
		System.out.println("sql=" + sql2);

		List<Object> userList = sessionFactory.openSession().selectList(statement.getId(),
				boundSql.getParameterObject());
		System.out.println(JSONObject.toJSONString(userList));

		// configuration.addMappedStatement(statement);
		Environment environment = configuration.getEnvironment();
		TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
		// environment.getDataSource().getConnection().getTransactionIsolation();
		Transaction tx = transactionFactory.newTransaction(environment.getDataSource(),
				TransactionIsolationLevel.REPEATABLE_READ, false);
		SimpleExecutor executor = new SimpleExecutor(configuration, tx);
		// List<Object> list = executor.query(statement, 16, RowBounds.DEFAULT, null);

		String sqlString = sqlSource.getBoundSql(params).getSql();
		System.out.println("sql=" + sqlString);
		List<Object> list = executor.doQuery(statement, params, RowBounds.DEFAULT, null, sqlSource.getBoundSql(params));
		System.out.println(JSONObject.toJSONString(list));

		// BoundSql countSql = new BoundSql(statement.getConfiguration(), "select
		// count(*) from (" + boundSql.getSql() + ") temp",
		// boundSql.getParameterMappings(), params);
		// Object r2 = executor.query(ms, parameterObject, rowBounds, resultHandler,
		// pageKey, boundSql);
	}

	/**
	 * 根据现有的 ms 创建一个新的，使用新的返回值类型
	 *
	 * @param ms
	 * @param resultType
	 * @return
	 */
	public MappedStatement newMappedStatement(MappedStatement ms, Class<?> resultType) {
		// 下面是新建的过程，考虑效率和复用对象的情况下，这里最后生成的ms可以缓存起来，下次根据 ms.getId() + "_" +
		// getShortName(resultType) 直接返回 ms,省去反复创建的过程
		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_Count",
				ms.getSqlSource(), ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuilder keyProperties = new StringBuilder();
			for (String keyProperty : ms.getKeyProperties()) {
				keyProperties.append(keyProperty).append(",");
			}
			keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
			builder.keyProperty(keyProperties.toString());
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		// count查询返回值int
		List<ResultMap> resultMaps = new ArrayList<ResultMap>();
		ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), resultType, EMPTY_RESULTMAPPING)
				.build();
		resultMaps.add(resultMap);
		builder.resultMaps(resultMaps);
		builder.resultSetType(ms.getResultSetType());
		builder.cache(ms.getCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());
		builder.useCache(ms.isUseCache());
		return builder.build();
	}

	private static final List<ResultMapping> EMPTY_RESULTMAPPING = new ArrayList<ResultMapping>(0);

	/**
	 * org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromConnection(
	 * ExecutorType, Connection)
	 * 
	 * @param environment
	 * @return
	 */
	private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
		if (environment == null || environment.getTransactionFactory() == null) {
			return new ManagedTransactionFactory();
		}
		return environment.getTransactionFactory();
	}

	private MappedStatement getQueryIdsMappedStatementForUpdateCache(MappedStatement mt) {
		String msId = mt.getId() + "_aaa";

		MappedStatement statement = null;
		Configuration configuration = mt.getConfiguration();
		try {
			statement = configuration.getMappedStatement(msId);
			if (statement != null) {
				return statement;
			}
		} catch (Exception e) {
		}

		synchronized (configuration) {
			/*
			 * if (configuration.hasStatement(msId)) { return
			 * configuration.getMappedStatement(msId); }
			 * 
			 * String sql = entityInfo.getMapperSqls().get(mt.getId());
			 */

			/*
			 * if (StringUtils.isNotBlank(sql)) { if
			 * (!sql.toLowerCase().contains(entityInfo.getTableName().toLowerCase())) {
			 * return null; } sql = "select " + entityInfo.getIdColumn() + " from " +
			 * entityInfo.getTableName() + " WHERE " + sql.split(WHERE_REGEX)[1]; sql =
			 * String.format(SqlTemplate.SCRIPT_TEMAPLATE, sql); } else { sql =
			 * PARSE_SQL_ERROR_DEFAULT; }
			 */
			String sql = "";
			SqlSource sqlSource = configuration.getDefaultScriptingLanguageInstance().createSqlSource(configuration,
					sql, Object.class);

			MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, msId, sqlSource,
					SqlCommandType.SELECT);

			statementBuilder.resource(mt.getResource());
			statementBuilder.fetchSize(mt.getFetchSize());
			statementBuilder.statementType(mt.getStatementType());
			statementBuilder.parameterMap(mt.getParameterMap());
			statement = statementBuilder.build();

			List<ResultMap> resultMaps = new ArrayList<ResultMap>();

			String id = msId + "-Inline";
			/*
			 * ResultMap.Builder builder = new ResultMap.Builder(configuration, id,
			 * entityInfo.getIdType(), new ArrayList<ResultMapping>(), true);
			 * resultMaps.add(builder.build()); MetaObject metaObject =
			 * SystemMetaObject.forObject(statement); metaObject.setValue("resultMaps",
			 * Collections.unmodifiableList(resultMaps));
			 */

			configuration.addMappedStatement(statement);

			return statement;
		}
	}

}